﻿using Casbin.NET.Adapter.Dapper.Extensions;
using Dapper.Contrib.Extensions;
using NetCasbin.Model;
using NetCasbin.Persist;
using System;

namespace Casbin.NET.Adapter.Dapper
{
    public class DapperAdapter<TCasbinRule> : IAdapter, IFilteredAdapter
        where TCasbinRule : class, ICasbinRule, new()
    {
        private readonly DapperDBContext dbContext;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="context"></param>
        public DapperAdapter(DapperDBContext dapperDBContext)
        {
            dbContext = dapperDBContext;
        }

        public bool IsFiltered { get; set; }

        /// <summary>
        /// 加载策略
        /// </summary>
        /// <param name="model"></param>
        public void LoadPolicy(Model model)
        {
            //此处如果集成多个系统的权限，可以改造CasbinRule表，然后用条件过滤(适合几万的用户量，不大的模式) 或者==>
            //将不同模型的策略分散在不同表中
            //如果是多租户的场景，官方推荐使用LoadFilteredPolicy方==>
            //具体请看https://casbin.org/docs/zh-CN/policy-subset-loading
            using (var connection = dbContext.CreateConnection())
            {
                var rules = connection.GetAll<TCasbinRule>().ToList();
                model.LoadPolicyFromCasbinRules(rules);
            }
        }

        /// <summary>
        /// 加载策略(异步)
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task LoadPolicyAsync(Model model)
        {
            using (var connection = dbContext.CreateConnection())
            {
                var rules = await connection.GetAllAsync<TCasbinRule>();
                model.LoadPolicyFromCasbinRules(rules.ToList());
            }
        }

        /// <summary>
        /// 加载过滤后的策略
        /// </summary>
        /// <param name="model"></param>
        /// <param name="filter"></param>
        public void LoadFilteredPolicy(Model model, Filter filter)
        {
            using (var connection = dbContext.CreateConnection())
            {
                var rules = connection.GetAll<TCasbinRule>().ApplyQueryFilter(filter);
                model.LoadPolicyFromCasbinRules(rules);
                IsFiltered = true;
            }
        }

        /// <summary>
        /// 加载过滤后的策略(异步)
        /// </summary>
        /// <param name="model"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public async Task LoadFilteredPolicyAsync(Model model, Filter filter)
        {
            using (var connection = dbContext.CreateConnection())
            {
                var rules = await connection.GetAllAsync<TCasbinRule>();
                model.LoadPolicyFromCasbinRules(rules.ApplyQueryFilter(filter));
                IsFiltered = true;
            }
        }

        /// <summary>
        /// 添加单个策略==>默认sec：p ptype=>P
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rule"></param>
        /// <exception cref="Exception"></exception>
        public void AddPolicy(string sec, string ptype, IList<string> rule)
        {
            try
            {
                if (rule is null || rule.Count is 0)
                {
                    return;
                }

                using (var connection = dbContext.CreateConnection())
                {
                    connection.Insert(OnAddPolicy(sec, ptype, rule, CasbinRuleExtenstion.Parse<TCasbinRule>(ptype, rule)));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 添加单个策略(异步)
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rule"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task AddPolicyAsync(string sec, string ptype, IList<string> rule)
        {
            try
            {
                if (rule is null || rule.Count is 0)
                {
                    return;
                }

                using (var connection = dbContext.CreateConnection())
                {
                    await connection.InsertAsync(OnAddPolicy(sec, ptype, rule, CasbinRuleExtenstion.Parse<TCasbinRule>(ptype, rule)));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 添加多个策略
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rules"></param>
        /// <exception cref="Exception"></exception>
        public void AddPolicies(string sec, string ptype, IEnumerable<IList<string>> rules)
        {
            try
            {
                using (var connection = dbContext.CreateConnection())
                {
                    if (rules is null)
                    {
                        return;
                    }

                    var rulesArray = rules as IList<string>[] ?? rules.ToArray();
                    if (rulesArray.Length is 0)
                    {
                        return;
                    }

                    var casbinRules = rulesArray.Select(r => CasbinRuleExtenstion.Parse<TCasbinRule>(ptype, r)).ToList();
                    connection.Insert(OnAddPolicies(sec, ptype, rules, casbinRules));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 添加多个策略(异步)
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rules"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task AddPoliciesAsync(string sec, string ptype, IEnumerable<IList<string>> rules)
        {
            try
            {
                using (var connection = dbContext.CreateConnection())
                {
                    if (rules is null)
                    {
                        return;
                    }

                    var rulesArray = rules as IList<string>[] ?? rules.ToArray();
                    if (rulesArray.Length is 0)
                    {
                        return;
                    }

                    var casbinRules = rulesArray.Select(r => CasbinRuleExtenstion.Parse<TCasbinRule>(ptype, r)).ToList();
                    await connection.InsertAsync(OnAddPolicies(sec, ptype, rules, casbinRules));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 保存策略
        /// </summary>
        /// <param name="model"></param>
        /// <exception cref="Exception"></exception>
        public void SavePolicy(Model model)
        {
            try
            {
                using (var connection = dbContext.CreateConnection())
                {
                    var casbinRules = new List<TCasbinRule>();
                    casbinRules.ReadPolicyFromCasbinModel(model);

                    if (casbinRules.Count is 0)
                    {
                        return;
                    }

                    var saveRules = OnSavePolicy(model, casbinRules);
                    connection.Update(saveRules);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 保存策略(异步)
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task SavePolicyAsync(Model model)
        {
            try
            {
                using (var connection = dbContext.CreateConnection())
                {
                    var casbinRules = new List<TCasbinRule>();
                    casbinRules.ReadPolicyFromCasbinModel(model);

                    if (casbinRules.Count is 0)
                    {
                        return;
                    }

                    var saveRules = OnSavePolicy(model, casbinRules);
                    await connection.UpdateAsync(saveRules);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 删除单条Policy
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rule"></param>
        /// <exception cref="Exception"></exception>
        public void RemovePolicy(string sec, string ptype, IList<string> rule)
        {
            try

            {
                if (rule is null || rule.Count is 0)
                {
                    return;
                }

                RemovePolicyInMemory(sec, ptype, rule);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 删除单条Policy(异步)
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rule"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task RemovePolicyAsync(string sec, string ptype, IList<string> rule)
        {
            try

            {
                if (rule is null || rule.Count is 0)
                {
                    return;
                }

                await RemovePolicyInMemoryAsync(sec, ptype, rule);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        public void RemoveFilteredPolicy(string sec, string ptype, int fieldIndex, params string[] fieldValues)
        {
            if (fieldValues == null || !fieldValues.Any())
                return;
            RemoveFilteredPolicyInMemory(sec, ptype, fieldIndex, fieldValues);
        }

        public async Task RemoveFilteredPolicyAsync(string sec, string ptype, int fieldIndex, params string[] fieldValues)
        {
            if (fieldValues == null || !fieldValues.Any())
                return;
            await RemoveFilteredPolicyInMemoryAsync(sec, ptype, fieldIndex, fieldValues);
        }

        /// <summary>
        /// 删除多条Policy
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rules"></param>
        /// <exception cref="NotImplementedException"></exception>
        public void RemovePolicies(string sec, string ptype, IEnumerable<IList<string>> rules)
        {
            try
            {
                if (rules is null)
                {
                    return;
                }

                var rulesArray = rules as IList<string>[] ?? rules.ToArray();
                if (rulesArray.Length is 0)
                {
                    return;
                }

                foreach (var rule in rulesArray)
                {
                    RemovePolicyInMemory(sec, ptype, rule);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 删除多条Policy(异步)
        /// </summary>
        /// <param name="sec"></param>
        /// <param name="ptype"></param>
        /// <param name="rules"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task RemovePoliciesAsync(string sec, string ptype, IEnumerable<IList<string>> rules)
        {
            try
            {
                if (rules is null)
                {
                    return;
                }

                var rulesArray = rules as IList<string>[] ?? rules.ToArray();
                if (rulesArray.Length is 0)
                {
                    return;
                }

                foreach (var rule in rulesArray)
                {
                    await RemovePolicyInMemoryAsync(sec, ptype, rule);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        private void RemovePolicyInMemory(string section, string policyType, IEnumerable<string> rule)
        {
            RemoveFilteredPolicy(section, policyType, 0, rule as string[] ?? rule.ToArray());
        }

        private async Task RemovePolicyInMemoryAsync(string section, string policyType, IEnumerable<string> rule)
        {
            await RemoveFilteredPolicyAsync(section, policyType, 0, rule as string[] ?? rule.ToArray());
        }

        private void RemoveFilteredPolicyInMemory(string section, string policyType, int fieldIndex, params string[] fieldValues)
        {
            using (var connection = dbContext.CreateConnection())
            {
                var query = connection.GetAll<TCasbinRule>().ToList().ApplyQueryFilter(policyType, fieldIndex, fieldValues);
                query = OnRemoveFilteredPolicy(section, policyType, fieldIndex, fieldValues, query).ToList();
                connection.Delete(query);
            }
        }

        private async Task RemoveFilteredPolicyInMemoryAsync(string section, string policyType, int fieldIndex, params string[] fieldValues)
        {
            using (var connection = dbContext.CreateConnection())
            {
                var query = connection.GetAll<TCasbinRule>().ToList().ApplyQueryFilter(policyType, fieldIndex, fieldValues);
                query = OnRemoveFilteredPolicy(section, policyType, fieldIndex, fieldValues, query).ToList();
                await connection.DeleteAsync(query);
            }
        }

        #region 虚方法

        protected virtual TCasbinRule OnAddPolicy(string section, string policyType, IEnumerable<string> rule, TCasbinRule casbinRule)
        {
            return casbinRule;
        }

        protected virtual IEnumerable<TCasbinRule> OnAddPolicies(string section, string policyType,
        IEnumerable<IEnumerable<string>> rules, IEnumerable<TCasbinRule> casbinRules)
        {
            return casbinRules;
        }

        protected virtual IEnumerable<TCasbinRule> OnSavePolicy(Model model, IEnumerable<TCasbinRule> casbinRules)
        {
            return casbinRules;
        }

        protected virtual IEnumerable<TCasbinRule> OnRemoveFilteredPolicy(string section, string policyType, int fieldIndex, string[] fieldValues, IEnumerable<TCasbinRule> casbinRules)
        {
            return casbinRules;
        }

        #endregion 虚方法
    }
}